<?php
/**
 * @copyright 2009-2019 Vanilla Forums Inc.
 * @license GPL-2.0-only
 */

namespace Vanilla\Formatting;

/**
 * Static utilities for text formatting.
 *
 * Do NOT put any stateful logic in this file.
 */
class FormatUtil
{
    /**
     * Do a preg_replace, but don't affect things inside <code> tags.
     *
     * The three parameters are identical to the ones you'd pass
     * preg_replace.
     *
     * @param mixed $search The value being searched for, just like in
     *              preg_replace or preg_replace_callback.
     * @param string|callable $replace The replacement value, just like in
     *              preg_replace or preg_replace_callback.
     * @param string $subject The string being searched.
     * @param bool $isCallback If true, do preg_replace_callback. Do
     *             preg_replace otherwise.
     * @return string
     */
    public static function replaceButProtectCodeBlocks(
        string $search,
        $replace,
        string $subject,
        bool $isCallback = false
    ): string {
        // Take the code blocks out, replace with a hash of the string, and
        // keep track of what substring got replaced with what hash.
        $codeBlockContents = [];
        $codeBlockHashes = [];
        $subject = preg_replace_callback(
            "/<code.*?>.*?<\/code>/is",
            function ($matches) use (&$codeBlockContents, &$codeBlockHashes) {
                // Surrounded by whitespace to try to prevent the characters
                // from being picked up by $Pattern.
                $replacementString = " " . sha1($matches[0]) . " ";
                $codeBlockContents[] = $matches[0];
                $codeBlockHashes[] = $replacementString;
                return $replacementString;
            },
            $subject
        );

        // Do the requested replacement.
        if ($isCallback) {
            $subject = preg_replace_callback($search, $replace, $subject);
        } else {
            $subject = preg_replace($search, $replace, $subject);
        }

        // Put back the code blocks.
        $subject = str_replace($codeBlockHashes, $codeBlockContents, $subject);

        return $subject;
    }

    /** @var array Unicode to ascii conversion table. */
    const CHAR_MAPPING = [
        "-" => " ",
        "_" => " ",
        "&lt;" => "",
        "&gt;" => "",
        "&#039;" => "",
        "&amp;" => "",
        "&quot;" => "",
        "À" => "A",
        "Á" => "A",
        "Â" => "A",
        "Ã" => "A",
        "Ä" => "Ae",
        "&Auml;" => "A",
        "Å" => "A",
        "Ā" => "A",
        "Ą" => "A",
        "Ă" => "A",
        "Æ" => "Ae",
        "Ç" => "C",
        "Ć" => "C",
        "Č" => "C",
        "Ĉ" => "C",
        "Ċ" => "C",
        "Ď" => "D",
        "Đ" => "D",
        "Ð" => "D",
        "È" => "E",
        "É" => "E",
        "Ê" => "E",
        "Ë" => "E",
        "Ē" => "E",
        "Ę" => "E",
        "Ě" => "E",
        "Ĕ" => "E",
        "Ė" => "E",
        "Ĝ" => "G",
        "Ğ" => "G",
        "Ġ" => "G",
        "Ģ" => "G",
        "Ĥ" => "H",
        "Ħ" => "H",
        "Ì" => "I",
        "Í" => "I",
        "Î" => "I",
        "Ï" => "I",
        "Ī" => "I",
        "Ĩ" => "I",
        "Ĭ" => "I",
        "Į" => "I",
        "İ" => "I",
        "Ĳ" => "IJ",
        "Ĵ" => "J",
        "Ķ" => "K",
        "Ł" => "K",
        "Ľ" => "K",
        "Ĺ" => "K",
        "Ļ" => "K",
        "Ŀ" => "K",
        "Ñ" => "N",
        "Ń" => "N",
        "Ň" => "N",
        "Ņ" => "N",
        "Ŋ" => "N",
        "Ò" => "O",
        "Ó" => "O",
        "Ô" => "O",
        "Õ" => "O",
        "Ö" => "Oe",
        "&Ouml;" => "Oe",
        "Ø" => "O",
        "Ō" => "O",
        "Ő" => "O",
        "Ŏ" => "O",
        "Œ" => "OE",
        "Ŕ" => "R",
        "Ř" => "R",
        "Ŗ" => "R",
        "Ś" => "S",
        "Š" => "S",
        "Ş" => "S",
        "Ŝ" => "S",
        "Ș" => "S",
        "Ť" => "T",
        "Ţ" => "T",
        "Ŧ" => "T",
        "Ț" => "T",
        "Ù" => "U",
        "Ú" => "U",
        "Û" => "U",
        "Ü" => "Ue",
        "Ū" => "U",
        "&Uuml;" => "Ue",
        "Ů" => "U",
        "Ű" => "U",
        "Ŭ" => "U",
        "Ũ" => "U",
        "Ų" => "U",
        "Ŵ" => "W",
        "Ý" => "Y",
        "Ŷ" => "Y",
        "Ÿ" => "Y",
        "Ź" => "Z",
        "Ž" => "Z",
        "Ż" => "Z",
        "Þ" => "T",
        "à" => "a",
        "á" => "a",
        "â" => "a",
        "ã" => "a",
        "ä" => "ae",
        "&auml;" => "ae",
        "å" => "a",
        "ā" => "a",
        "ą" => "a",
        "ă" => "a",
        "æ" => "ae",
        "ç" => "c",
        "ć" => "c",
        "č" => "c",
        "ĉ" => "c",
        "ċ" => "c",
        "ď" => "d",
        "đ" => "d",
        "ð" => "d",
        "è" => "e",
        "é" => "e",
        "ê" => "e",
        "ë" => "e",
        "ē" => "e",
        "ę" => "e",
        "ě" => "e",
        "ĕ" => "e",
        "ė" => "e",
        "ƒ" => "f",
        "ĝ" => "g",
        "ğ" => "g",
        "ġ" => "g",
        "ģ" => "g",
        "ĥ" => "h",
        "ħ" => "h",
        "ì" => "i",
        "í" => "i",
        "î" => "i",
        "ï" => "i",
        "ī" => "i",
        "ĩ" => "i",
        "ĭ" => "i",
        "į" => "i",
        "ı" => "i",
        "ĳ" => "ij",
        "ĵ" => "j",
        "ķ" => "k",
        "ĸ" => "k",
        "ł" => "l",
        "ľ" => "l",
        "ĺ" => "l",
        "ļ" => "l",
        "ŀ" => "l",
        "ñ" => "n",
        "ń" => "n",
        "ň" => "n",
        "ņ" => "n",
        "ŉ" => "n",
        "ŋ" => "n",
        "ò" => "o",
        "ó" => "o",
        "ô" => "o",
        "õ" => "o",
        "ö" => "oe",
        "&ouml;" => "oe",
        "ø" => "o",
        "ō" => "o",
        "ő" => "o",
        "ŏ" => "o",
        "œ" => "oe",
        "ŕ" => "r",
        "ř" => "r",
        "ŗ" => "r",
        "š" => "s",
        "ù" => "u",
        "ú" => "u",
        "û" => "u",
        "ü" => "ue",
        "ū" => "u",
        "&uuml;" => "ue",
        "ů" => "u",
        "ű" => "u",
        "ŭ" => "u",
        "ũ" => "u",
        "ų" => "u",
        "ŵ" => "w",
        "ý" => "y",
        "ÿ" => "y",
        "ŷ" => "y",
        "ž" => "z",
        "ż" => "z",
        "ź" => "z",
        "þ" => "t",
        "ß" => "ss",
        "ſ" => "ss",
        "ый" => "iy",
        "А" => "A",
        "Б" => "B",
        "В" => "V",
        "Г" => "G",
        "Д" => "D",
        "Е" => "E",
        "Ё" => "YO",
        "Ж" => "ZH",
        "З" => "Z",
        "И" => "I",
        "І" => "I",
        "Й" => "Y",
        "К" => "K",
        "Л" => "L",
        "М" => "M",
        "Н" => "N",
        "О" => "O",
        "П" => "P",
        "Р" => "R",
        "С" => "S",
        "Т" => "T",
        "У" => "U",
        "Ф" => "F",
        "Х" => "H",
        "Ц" => "C",
        "Ч" => "CH",
        "Ш" => "SH",
        "Щ" => "SCH",
        "Ъ" => "",
        "Ы" => "Y",
        "Ь" => "",
        "Э" => "E",
        "Ю" => "YU",
        "Я" => "YA",
        "Є" => "YE",
        "Ї" => "YI",
        "а" => "a",
        "б" => "b",
        "в" => "v",
        "г" => "g",
        "д" => "d",
        "е" => "e",
        "ё" => "yo",
        "ж" => "zh",
        "з" => "z",
        "и" => "i",
        "і" => "i",
        "й" => "y",
        "к" => "k",
        "л" => "l",
        "м" => "m",
        "н" => "n",
        "о" => "o",
        "п" => "p",
        "р" => "r",
        "с" => "s",
        "т" => "t",
        "у" => "u",
        "ф" => "f",
        "х" => "h",
        "ц" => "c",
        "ч" => "ch",
        "ш" => "sh",
        "щ" => "sch",
        "ъ" => "",
        "ы" => "y",
        "ь" => "",
        "э" => "e",
        "є" => "ye",
        "ю" => "yu",
        "я" => "ya",
        "ї" => "yi",
    ];

    /**
     * Convert certain unicode characters into their ascii equivalents.
     *
     * @param string $text The text to clean.
     * @return string
     */
    public static function transliterate(string $text): string
    {
        $text = strtr($text, self::CHAR_MAPPING);
        $text = preg_replace("/[^A-Za-z0-9 ]/", "", urldecode($text));
        $text = preg_replace("/ +/", "-", trim($text));
        return strtolower($text);
    }
}
